package com.intelligent.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.intelligent.mapper.*;
import com.intelligent.pojo.*;
import com.intelligent.service.CustomerReconciliationDetailService;
import com.intelligent.service.SalesOrdersService;
import com.intelligent.vo.ReconciliationDetailPageReq;
import com.intelligent.vo.ReconciliationDetailResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class CustomerReconciliationDetailServiceImpl extends ServiceImpl<CustomerReconciliationDetailMapper, CustomerReconciliationDetail>
        implements CustomerReconciliationDetailService {

    @Autowired
    private CustomerReconciliationMapper reconciliationMapper;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OutboundDetailMapper outboundDetailMapper;
    @Autowired
    private ReceiptOrderMapper receiptOrderMapper;
    @Autowired
    private SalesOrdersService salesOrdersService;

    @Override
    public boolean addDetail(CustomerReconciliationDetail detail) {
        BigDecimal amount = detail.getAmount() == null ? BigDecimal.ZERO : detail.getAmount();
        BigDecimal received = detail.getReceivedAmount() == null ? BigDecimal.ZERO : detail.getReceivedAmount();
        detail.setToReceiveAmount(amount.subtract(received));
        return this.save(detail);
    }

    @Override
    public Page<ReconciliationDetailResp> pageDetails(ReconciliationDetailPageReq req) {
        List<Long> matchedRecoIds = null;
        if (req.getRecoCode() != null && !req.getRecoCode().trim().isEmpty()) {
            LambdaQueryWrapper<CustomerReconciliation> rcw = new LambdaQueryWrapper<>();
            rcw.like(CustomerReconciliation::getRecoCode, req.getRecoCode().trim());
            List<CustomerReconciliation> recos = reconciliationMapper.selectList(rcw);
            matchedRecoIds = recos.stream().map(CustomerReconciliation::getRecoId).collect(Collectors.toList());
            if (matchedRecoIds.isEmpty()) {
                return empty(req);
            }
        }

        Page<CustomerReconciliationDetail> entityPage = new Page<>(req.getPageNum(), req.getPageSize());
        LambdaQueryWrapper<CustomerReconciliationDetail> dw = new LambdaQueryWrapper<>();
        if (matchedRecoIds != null) dw.in(CustomerReconciliationDetail::getReconciliationId, matchedRecoIds);
        dw.orderByDesc(CustomerReconciliationDetail::getId);
        this.page(entityPage, dw);

        if (entityPage.getRecords().isEmpty()) return empty(req);

        Map<Long, CustomerReconciliation> recoMap = mapRecos(entityPage.getRecords());
        Map<Long, Product> productMap = mapProducts(entityPage.getRecords());
        Map<Long, OutboundDetail> outboundMap = mapOutbounds(entityPage.getRecords());
        Map<Long, ReceiptOrder> receiptMap = mapReceipts(entityPage.getRecords());

        List<ReconciliationDetailResp> rows = entityPage.getRecords().stream()
                .map(d -> build(d, recoMap, productMap, outboundMap, receiptMap))
                .collect(Collectors.toList());

        Page<ReconciliationDetailResp> vo = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
        vo.setRecords(rows);
        return vo;
    }

    @Override
    public ReconciliationDetailResp getDetail(Long id) {
        CustomerReconciliationDetail d = this.getById(id);
        if (d == null) return null;
        Map<Long, CustomerReconciliation> recoMap = mapRecos(Collections.singletonList(d));
        Map<Long, Product> productMap = mapProducts(Collections.singletonList(d));
        Map<Long, OutboundDetail> outboundMap = mapOutbounds(Collections.singletonList(d));
        Map<Long, ReceiptOrder> receiptMap = mapReceipts(Collections.singletonList(d));
        return build(d, recoMap, productMap, outboundMap, receiptMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateFromReconciliation(Long reconciliationId) {
        CustomerReconciliation rc = reconciliationMapper.selectById(reconciliationId);
        if (rc == null) throw new RuntimeException("对账单不存在");
        Long customerId = rc.getCustomerId();
        Date start = rc.getCreateDate();
        Date end = rc.getEndDate();

        // 周期内该客户的销售订单
        LambdaQueryWrapper<SalesOrders> sow = new LambdaQueryWrapper<>();
        sow.eq(SalesOrders::getCustomerId, customerId)
           .between(SalesOrders::getOrderDate, start, end);
        List<SalesOrders> orders = salesOrdersService.list(sow);
        if (orders.isEmpty()) return;

        List<CustomerReconciliationDetail> batch = new ArrayList<>();
        for (SalesOrders so : orders) {
            CustomerReconciliationDetail d = new CustomerReconciliationDetail();
            d.setReconciliationId(reconciliationId);
            d.setSalesOrderId(so.getId());
            d.setAmount(so.getOrderAmount());

            // 出库明细：根据 orderId 取一条，带出 productId
            LambdaQueryWrapper<OutboundDetail> odw = new LambdaQueryWrapper<>();
            odw.eq(OutboundDetail::getOrderId, so.getId()).last("limit 1");
            OutboundDetail od = outboundDetailMapper.selectOne(odw);
            if (od != null) {
                d.setOutboundOrderId(od.getId());
                d.setProductId(od.getProductId());
            }

            // 收款单：根据 relatedSalesOrder = 销售单号 聚合该订单收款
            LambdaQueryWrapper<ReceiptOrder> row = new LambdaQueryWrapper<>();
            row.eq(ReceiptOrder::getRelatedSalesOrder, so.getOrderNo());
            List<ReceiptOrder> receipts = receiptOrderMapper.selectList(row);
            BigDecimal received = receipts.stream()
                    .map(ReceiptOrder::getWrittenOffAmount)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            if (!receipts.isEmpty()) {
                d.setReceiptOrderId(receipts.get(0).getId().longValue());
            }
            d.setReceivedAmount(received);

            BigDecimal amount = d.getAmount() == null ? BigDecimal.ZERO : d.getAmount();
            d.setToReceiveAmount(amount.subtract(received));
            d.setCreateTime(new Date());
            batch.add(d);
        }
        if (!batch.isEmpty()) this.saveBatch(batch);
    }

    private ReconciliationDetailResp build(CustomerReconciliationDetail d,
                                           Map<Long, CustomerReconciliation> recoMap,
                                           Map<Long, Product> productMap,
                                           Map<Long, OutboundDetail> outboundMap,
                                           Map<Long, ReceiptOrder> receiptMap) {
        ReconciliationDetailResp r = new ReconciliationDetailResp();
        r.setId(d.getId());
        r.setReconciliationId(d.getReconciliationId());
        CustomerReconciliation rc = recoMap.get(d.getReconciliationId());
        if (rc != null) r.setRecoCode(rc.getRecoCode());
        r.setProductId(d.getProductId());
        if (d.getProductId() != null) {
            Product p = productMap.get(d.getProductId());
            if (p != null) r.setProductDisplay(p.getProductName() + " " + p.getProductCode());
        }
        r.setOutboundOrderId(d.getOutboundOrderId());
        if (d.getOutboundOrderId() != null) {
            OutboundDetail od = outboundMap.get(d.getOutboundOrderId());
            if (od != null) r.setOutboundNo(od.getOutboundNo());
        }
        r.setReceiptOrderId(d.getReceiptOrderId());
        if (d.getReceiptOrderId() != null) {
            ReceiptOrder ro = receiptMap.get(d.getReceiptOrderId());
            if (ro != null) r.setReceiptOrderNo(ro.getReceiptOrderNo());
        }
        r.setAmount(d.getAmount());
        r.setReceivedAmount(d.getReceivedAmount());
        r.setToReceiveAmount(d.getToReceiveAmount());
        return r;
    }

    private Map<Long, CustomerReconciliation> mapRecos(List<CustomerReconciliationDetail> list) {
        Set<Long> ids = list.stream().map(CustomerReconciliationDetail::getReconciliationId).filter(Objects::nonNull).collect(Collectors.toSet());
        return ids.isEmpty() ? new HashMap<>() : reconciliationMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(CustomerReconciliation::getRecoId, v -> v));
    }

    private Map<Long, Product> mapProducts(List<CustomerReconciliationDetail> list) {
        Set<Long> ids = list.stream().map(CustomerReconciliationDetail::getProductId).filter(Objects::nonNull).collect(Collectors.toSet());
        return ids.isEmpty() ? new HashMap<>() : productMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(Product::getProductId, v -> v));
    }

    private Map<Long, OutboundDetail> mapOutbounds(List<CustomerReconciliationDetail> list) {
        Set<Long> ids = list.stream().map(CustomerReconciliationDetail::getOutboundOrderId).filter(Objects::nonNull).collect(Collectors.toSet());
        return ids.isEmpty() ? new HashMap<>() : outboundDetailMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(OutboundDetail::getId, v -> v));
    }

    private Map<Long, ReceiptOrder> mapReceipts(List<CustomerReconciliationDetail> list) {
        Set<Long> ids = list.stream().map(CustomerReconciliationDetail::getReceiptOrderId).filter(Objects::nonNull).collect(Collectors.toSet());
        if (ids.isEmpty()) return new HashMap<>();
        List<Integer> intIds = ids.stream().map(Long::intValue).collect(Collectors.toList());
        List<ReceiptOrder> ros = intIds.isEmpty() ? new ArrayList<>() : receiptOrderMapper.selectBatchIds(intIds);
        Map<Long, ReceiptOrder> map = new HashMap<>();
        for (ReceiptOrder ro : ros) map.put(ro.getId().longValue(), ro);
        return map;
    }

    private Page<ReconciliationDetailResp> empty(ReconciliationDetailPageReq req) {
        Page<ReconciliationDetailResp> p = new Page<>(req.getPageNum(), req.getPageSize(), 0);
        p.setRecords(new ArrayList<>());
        return p;
    }
}
